#define AUTO_GAIN 0 // Auto volume adjustment (disabled for manual control) #define VOL_THR 25 // Silence threshold (no display on matrix below this) #define LOW_PASS 20 // Lower sensitivity threshold for noise (no jumps when no sound) #define DEF_GAIN 80 // Default maximum threshold (ignored when GAIN_CONTROL is active) #define FHT_N 256 // Spectrum width x2 #define LOG_OUT 1 #define PEAK_HOLD_TIME 2000 // Peak hold time in ms // Button pins #define BUTTON1 8 #define BUTTON2 9 #define BUTTON3 10 // Manually defined array of tones, first smooth, then steeper byte posOffset[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; // 1500 Hz //byte posOffset[16] = {1, 2, 3, 4, 6, 8, 10, 13, 16, 20, 25, 30, 35, 40, 45, 50}; // 4000 Hz #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #include #include // http://rcl-radio.ru/wp-content/uploads/2023/04/U8glib.zip #include // http://forum.rcl-radio.ru/misc.php?action=pan_download&item=297&download=1 #define EN 6 #define RW 5 #define CS 4 //U8GLIB_SH1106_128X64 lcd(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_FAST); // Dev 0, Fast I2C / TWI U8GLIB_ST7920_128X64_1X lcd(EN, RW, CS); // serial use, PSB = GND byte gain = DEF_GAIN; unsigned long gainTimer, times; byte maxValue, maxValue_f; float k = 0.1; byte ur[16], urr[16]; // Button state variables bool button1State = false; bool button2State = false; bool button3State = false; unsigned long button1Time = 0; unsigned long button2Time = 0; unsigned long button3Time = 0; // Mode variables byte displayMode = 0; // 0=normal, 1=peak hold, 2=falling dots, 3=symmetrical byte speedMode = 0; // 0=normal, 1=fast, 2=slow byte sensitivityMode = 0; // 0=normal, 1=high, 2=low byte peakHold[16]; // Peak hold values for each band unsigned long peakTimer[16]; // Timer for peak decay void setup() { delay(100); sbi(ADCSRA, ADPS2); cbi(ADCSRA, ADPS1); sbi(ADCSRA, ADPS0); Serial.begin(9600); Wire.begin(); Wire.setClock(800000L); lcd.begin(); // lcd.setRot180(); lcd.setFont(u8g_font_profont11r); analogReadResolution(10); // ADC 10 BIT analogReference(INTERNAL1V024); pinMode(A0, INPUT); // INPUT AUDIO // Initialize button pins pinMode(BUTTON1, INPUT_PULLUP); pinMode(BUTTON2, INPUT_PULLUP); pinMode(BUTTON3, INPUT_PULLUP); // Initialize peak hold array for(int i = 0; i < 16; i++) { peakHold[i] = 0; peakTimer[i] = 0; } } void handleButtons() { // Button 1 - Display Mode Cycle if (digitalRead(BUTTON1) == LOW) { if (millis() - button1Time > 300) { // Debounce displayMode = (displayMode + 1) % 4; // Cycle through 4 modes button1Time = millis(); } } // Button 2 - Speed Mode Cycle if (digitalRead(BUTTON2) == LOW) { if (millis() - button2Time > 300) { speedMode = (speedMode + 1) % 3; // Cycle through 3 speed modes button2Time = millis(); } } // Button 3 - Sensitivity Cycle if (digitalRead(BUTTON3) == LOW) { if (millis() - button3Time > 300) { sensitivityMode = (sensitivityMode + 1) % 3; // Cycle through 3 sensitivity modes button3Time = millis(); // Adjust gain based on sensitivity switch(sensitivityMode) { case 0: gain = DEF_GAIN; break; // Normal case 1: gain = DEF_GAIN / 2; break; // High sensitivity case 2: gain = DEF_GAIN * 2; break; // Low sensitivity } } } } void updatePeakHold() { for (int i = 0; i < 16; i++) { int posLevel = map(fht_log_out[posOffset[i]], LOW_PASS, gain, 0, 60); posLevel = constrain(posLevel, 0, 60); if (posLevel > peakHold[i]) { peakHold[i] = posLevel; peakTimer[i] = millis(); } else if (millis() - peakTimer[i] > PEAK_HOLD_TIME) { if (peakHold[i] > 0) peakHold[i]--; } } } void drawModeIndicators() { // Display mode indicators at top right lcd.setFont(u8g_font_04b_03); // Display mode indicator (N, P, D, S) char modeChar = 'N'; switch(displayMode) { case 0: modeChar = 'N'; break; // Normal case 1: modeChar = 'P'; break; // Peak case 2: modeChar = 'D'; break; // Dot case 3: modeChar = 'S'; break; // Symmetrical } // Speed mode indicator (N, F, S) char speedChar = 'N'; switch(speedMode) { case 0: speedChar = 'N'; break; // Normal case 1: speedChar = 'F'; break; // Fast case 2: speedChar = 'S'; break; // Slow } // Sensitivity indicator (N, H, L) char sensChar = 'N'; switch(sensitivityMode) { case 0: sensChar = 'N'; break; // Normal case 1: sensChar = 'H'; break; // High case 2: sensChar = 'L'; break; // Low } // Draw all three indicators at top right lcd.drawStr(100, 5, String(modeChar).c_str()); lcd.drawStr(110, 5, String(speedChar).c_str()); lcd.drawStr(120, 5, String(sensChar).c_str()); } void drawSpectrum() { lcd.firstPage(); do { for (int pos = 0; pos < 128; pos += 8) { int band = pos / 8; int posLevel = map(fht_log_out[posOffset[band]], LOW_PASS, gain, 0, 60); posLevel = constrain(posLevel, 0, 60); if(millis() - times < 2000) { posLevel = 60; // Startup animation } urr[band] = posLevel; // Apply speed mode to falling effect int fallSpeed = 1; switch(speedMode) { case 0: fallSpeed = 1; break; // Normal case 1: fallSpeed = 3; break; // Fast fall case 2: fallSpeed = 1; if(random(2) == 0) fallSpeed = 0; break; // Slow/random } if(urr[band] < ur[band]) { ur[band] = max(ur[band] - fallSpeed, 0); } else { ur[band] = posLevel; } delayMicroseconds(200); // Draw based on display mode switch(displayMode) { case 0: // Normal bars for (int v_pos = 0; v_pos < ur[band] + 4; v_pos += 4) { lcd.drawBox(pos, 61 - v_pos, 6, 2); } break; case 1: // Peak hold with bars for (int v_pos = 0; v_pos < ur[band] + 4; v_pos += 4) { lcd.drawBox(pos, 61 - v_pos, 6, 2); } // Draw peak dots if(peakHold[band] > 0) { lcd.drawBox(pos + 1, 61 - peakHold[band], 4, 1); } break; case 2: // Falling dots for (int v_pos = 0; v_pos < ur[band]; v_pos += 4) { lcd.drawBox(pos + 1, 61 - v_pos, 4, 1); } break; case 3: // Symmetrical mode for (int v_pos = 0; v_pos < ur[band] + 4; v_pos += 4) { lcd.drawBox(pos, 61 - v_pos, 6, 2); lcd.drawBox(pos, 3 + v_pos, 6, 2); // Mirror at top } break; } } // Draw mode indicators at top right drawModeIndicators(); } while(lcd.nextPage()); } void loop() { analyzeAudio(); handleButtons(); updatePeakHold(); drawSpectrum(); if (AUTO_GAIN) { maxValue_f = maxValue * k + maxValue_f * (1 - k); if (millis() - gainTimer > 1500) { if (maxValue_f > VOL_THR) gain = maxValue_f; else gain = 100; gainTimer = millis(); } } } void analyzeAudio() { for (int i = 0 ; i < FHT_N ; i++) { int sample = analogRead(A0); fht_input[i] = sample; // put real data into bins } fht_window(); // window the data for better frequency response fht_reorder(); // reorder the data before doing the fht fht_run(); // process the data in the fht fht_mag_log(); // take the output of the fht }